Skip to content

fix(app-shell): a flow that dropped a read-only field warns the person who clicked, naming the field by its label - #33064

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-txkk36
Nov 13, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-txkk36

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Elon · project thread

Fixes #33009
Fixes #32861

Clause-②: yes (widening: the parser is exported from @object-ui/data-objectstack's entry; an i18n key, if added, widens too; the dev measures and reports)
Metadata-protocol: none

Measured: the widening is three new exports of @object-ui/data-objectstack (isWireDroppedFieldsEntry, asDroppedFieldsNotice, type WireDroppedFieldsEntry), unchanged in body; no i18n key is added (the existing detail.writeStripped* wording is reused).

维护者速读

  • 改了什么:控制台读流程回答里的 droppedFields。员工从记录按钮启动流程(列表/页面和记录页两处),或在流程屏幕点「提交」继续,若流程的写入步骤丢掉了只读字段,会弹出黄色警告,用字段的中文名说明哪个字段没写进去。
  • 为什么改:A runtime-authored flow that writes a readonly field as the user publishes clean; the button runs, reports success and changes nothing (flow-update-readonly-field is not run at the save/publish gate) #32666 的「提交审批」场景:流程写只读的「状态」,写入被丢掉,按钮却答成功,记录仍是「草稿」,员工什么也看不到。服务端 fix(automation): a flow run's answer names the read-only fields its write steps dropped (#32861) #33007 已在回答里带上被丢字段,这里让控制台显示出来。
  • 风险与回滚:只加提示,不改流程结果、不改刷新和成功提示;回答里没有该字段(干净运行或旧服务端)时与以前完全一样;失败的运行不加这条提示。回滚即还原本 PR。
  • 先看:ui/packages/app-shell/src/utils/flowLaunch.ts(新读取器 announceFlowDroppedFields 及 settled 里的调用)、ui/packages/app-shell/src/views/FlowRunner.tsx(继续运行时的调用)、ui/packages/data-objectstack/src/index.ts(只加 export)。
  • 两分钟验证:在 ui/ 下跑 pnpm exec vitest run packages/app-shell/src/hooks/__tests__/useConsoleActionRuntime.flowDroppedFields-33009.test.tsx,应见 5 passed。
  • 需要你做的:无,契约复审由分诊席做,席位落地。

What changes

  • @object-ui/data-objectstack: the existing droppedFields wire gate and parser (isWireDroppedFieldsEntry, asDroppedFieldsNotice, with its skew arm for an unknown reason) and their shared WireDroppedFieldsEntry type are exported. Bodies unchanged; no second copy anywhere.
  • @object-ui/app-shell utils/flowLaunch.ts: one reader, announceFlowDroppedFields(json, { t, fieldLabel, dataSource }), parses data.droppedFields with that parser and speaks it through the existing writeWarningToast.emitWriteWarning (the record save's own title, per-reason sentence and label lookup), one event per object the entries name. launchConsoleFlow calls it in settled() for both doors (action door and trigger route), so both launch hosts (useConsoleActionRuntime, RecordDetailView) are covered by one call; it is skipped on a failed launch. Not awaited, so the label lookup never holds the click's outcome or its refresh.
  • views/FlowRunner.tsx: the resume answer goes through the same reader (every non-failed answer).
  • Hosts: useConsoleActionRuntime and RecordDetailView pass { t, fieldLabel, dataSource } into launchConsoleFlow.
  • Wording: the existing detail.writeStrippedTitle / detail.writeStripped* keys; no new key.

Label source chosen (the order asked which): the host's dataSource. In the console it IS the ObjectStackAdapter (AppContent / ConsoleShell: const dataSource = useAdapter()), every host already holds it (it renders their forms; FlowRunner already takes it as a prop), and its cached getObjectSchema is exactly what AdapterProvider hands the save toast. So no host gains a dependency, and the flow toast labels fields by the same path the save toast does. A data source without getObjectSchema names fields by api name. Alternatives measured and not taken: building a schema source from the hosts' objects (would miss an object the flow writes that is not in the loaded list, and would be a second label path); useAdapter() inside each host (a new hook call in three places for a value they already hold).

Per object, not per click: a flow can write any object, so each entry is labelled on the object it names; an off-spec entry with no object is named by api name rather than guessed onto the clicked record's object (where status would get the wrong object's label).

Tests

New, all against the real runtime (real ActionRunner, flow handler, launch module, reader, writeWarningToast, FlowRunner, en pack; only the network, router, toaster and getObjectSchema stubbed):

Ablation (fix commented out, red, restored by `scripts/ablation-replace.mjs`)

ui tests import @object-ui/* source, so no dist rebuild sits between mutation and run.

Acceptance notes

Verification

All local runs on head ab4b0720 (origin/main c54a23d2 merged; main had not moved since the branch point).

  • dispatch-gates --commands derived 38 families; all 38 ran, exit 0 each; --ran reconciliation: "38 run, 0 NOT-MEASURED (a DERIVED zero — all 38 recorded an exit code and none of them is 3)". Includes node scripts/check-sdui-manifest.mjs (exit 0) and pnpm check:test-source-alias (exit 0).
  • pnpm --filter @object-ui/data-objectstack run type-check :: exit 0; pnpm --filter @object-ui/app-shell run type-check (src + tsconfig.test.json) :: exit 0.
  • vitest run packages/data-objectstack/ :: 119 files, 1376 tests passed.
  • vitest run app-shell utils/, hooks/, providers/, views/__tests__/FlowRunner*, views/RecordDetailView.flow*, views/RecordDetailView.apiFlowRefusal*, flow-envelope.ratchet.test.ts :: 217 files passed, 1 skipped; 2276 tests passed, 8 skipped. The rest of views/ (does not finish on the shared box) is declared to CI.
  • Repo-wide pnpm lint and the full farm: CI.

…n who clicked, naming the field

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WV4cwVkQ2wgvSKnhkFnys
…h, inside the claimed file surface

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WV4cwVkQ2wgvSKnhkFnys
@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface labels Nov 13, 2026
…c names the reader where it lives

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WV4cwVkQ2wgvSKnhkFnys
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#1 · os-elon-musk · session_016WV4cwxNJ1s5i1doop7KEv

Contract review

Served-tier: TIER_DEFAULT
Head-sha: 0f69f38ae7f1d3f63c1503eea68f008c1158615b
Local-runs: none

① Derived judgments

  • The face is the Clause-②: yes (widening) declaration alone (triage#1 declined this face as the lane's own record, 6846353637). @object-ui/data-objectstack has one entry (exports["."] → dist/index.*, built from src/index.ts), and three of its existing names gain export: the function isWireDroppedFieldsEntry, the function asDroppedFieldsNotice, and the type WireDroppedFieldsEntry they share. Their bodies are unchanged in this diff (only the export keyword and TSDoc lines move), so the published set grows by three names and nothing already published is removed, renamed or narrowed. DroppedFieldsNotice was already exported.
  • @object-ui/app-shell: the new announceFlowDroppedFields / flowAnswerDroppedFields / FlowDroppedFieldsContext live in src/utils/flowLaunch.ts; ui/packages/app-shell/src/index.ts at this head re-exports nothing from flowLaunch, so they are internal. No i18n key is added (the toast reuses the record save's existing wording). No packages/spec path, no metadata.

② Semver level

  • patch for @object-ui/data-objectstack and @object-ui/app-shell (AGENTS.md §3: the level follows Metadata-protocol alone; none takes patch, and a non-protocol widening ships patch). The changeset now says patch for both (rework at this head).

③ Boundary flags

Implemented-by: claude/project-thread-txkk36
Reviewed-by: session_016WV4cwxNJ1s5i1doop7KEv

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#1 · os-elon-musk · session_016WV4cwxNJ1s5i1doop7KEv

席位核对 · ACCEPT · domain:ui#1 · 2026-11-13T07:14Z · head 0f69f38ae7f1d3f63c1503eea68f008c1158615b

  • Shape: draft → main, Fixes #33009 and Fixes #32861 (the parent closes when its console half lands, as console: a record-button flow whose write step dropped a read-only field shows a warning toast naming the fields (console half of #32861) #33009 says). Files: ui/packages/data-objectstack/src/index.ts (three export keywords + TSDoc), ui/packages/app-shell/src/utils/flowLaunch.ts, hooks/useConsoleActionRuntime.tsx, views/RecordDetailView.tsx, views/FlowRunner.tsx, four tests, one root .changeset/ (both packages patch). Inside the claim's surface; no server code.
  • Seat's own read of the diff: one reader, announceFlowDroppedFields in flowLaunch.ts, parses the answer's data.droppedFields with @object-ui/data-objectstack's own gate and parser (no second copy), groups by the object each entry names (a flow can write any object, so a label is looked up on that object, never the clicked record's), and speaks through writeWarningToast.emitWriteWarning, the record save's own warning. Labels come from the host's dataSource (getObjectSchema), which every host already holds; without it fields are named by api name. Both launch hosts pass it through launchConsoleFlow; FlowRunner's resume calls it fire-and-forget, so the label lookup never holds the next screen. The reader never throws. An answer without droppedFields shows exactly what it did.
  • Clause-②: yes (widening), contract review TIER_DEFAULT PASS 6846369467 on this head (triage#1 declined this face as the lane's own record, 6846353637).
  • Gates (check-runs on this head): 58 runs, 45 success, 13 skipped, 0 red. Dev's local: 38 derived gates + check-sdui-manifest + both type-checks exit 0; ablations A/B/C red then restored (report 6846301512); rework round reran the changeset gates and the data-objectstack type-check, exit 0.
  • Rework taken: changeset @object-ui/data-objectstack minor → patch (AGENTS.md §3); the parser's TSDoc named a non-existent utils/flowDroppedFields, now announceFlowDroppedFields in utils/flowLaunch (it ships in the .d.ts).
  • Accepted deviations: browser check not measured (the A runtime-authored flow that writes a readonly field as the user publishes clean; the button runs, reports success and changes nothing (flow-update-readonly-field is not run at the save/publish gate) #32666 shape is runtime-authored); card item 4 (a paused subflow's drops) is the server's automation: a flow's answer carries the dropped-field fact of its map items and of a subflow child that paused and resumed (delegated half of #32861) #33019, nothing owed here.
  • Out of scope / landing: none filed. check-governed-merges --pr 33064: NOT governed, +704/−9. K = 3 behind main, no merge owed. Lands by the seat: ready + auto-merge into the merge queue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

2 participants